/*-------------------------------------------------------------------------
 *
 * unaccent.c
 *	  文本搜索去重音字典
 *
 * Copyright (c) 2009-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/unaccent/unaccent.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/namespace.h"
#include "catalog/pg_ts_dict.h"
#include "commands/defrem.h"
#include "lib/stringinfo.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_public.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/syscache.h"

PG_MODULE_MAGIC;

/*
 * 一个无重音字典使用trie来查找替换字符串。trie的每个节点
 * 是一个包含256个TrieChar结构的数组；数组的第N个元素
 * 对应于下一个字节值N。该元素可以包含替换字符串（用于源字符串以此字节结尾时）
 * 和指向另一个trie节点的链接（如果还有更多字节，则应跟随此链接）。
 *
 * 请注意，trie搜索逻辑不关注多字节字符边界。
 * 只要输入到trie中的数据和我们尝试查找的数据编码有效，
 * 就不会发生部分字符匹配。
 */
typedef struct TrieChar
{
	struct TrieChar *nextChar;
	char	   *replaceTo;
	int			replacelen;
} TrieChar;

/*
 * placeChar - 将str逐字节放入trie的结构中。
 *
 * 如果节点为NULL，我们需要创建一个新节点，并返回该节点；
 * 否则返回值与节点相同。
 */
static TrieChar * fc_placeChar(TrieChar *fc_node, const unsigned char *fc_str, int fc_lenstr,
		  const char *fc_replaceTo, int fc_replacelen)
{
	TrieChar   *fc_curnode;

	if (!fc_node)
		fc_node = (TrieChar *) palloc0(sizeof(TrieChar) * 256);

	Assert(fc_lenstr > 0);			/* 否则str[0]不存在 */

	fc_curnode = fc_node + *fc_str;

	if (fc_lenstr <= 1)
	{
		if (fc_curnode->replaceTo)
			ereport(WARNING,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("duplicate source strings, first one will be used")));
		else
		{
			fc_curnode->replacelen = fc_replacelen;
			fc_curnode->replaceTo = (char *) palloc(fc_replacelen);
			memcpy(fc_curnode->replaceTo, fc_replaceTo, fc_replacelen);
		}
	}
	else
	{
		fc_curnode->nextChar = fc_placeChar(fc_curnode->nextChar, fc_str + 1, fc_lenstr - 1,
									  fc_replaceTo, fc_replacelen);
	}

	return fc_node;
}

/*
 * initTrie  - 从文件创建trie。
 *
 * 函数将UTF8编码的文件转换为当前编码。
 */
static TrieChar * fc_initTrie(const char *fc_filename)
{
	TrieChar   *volatile fc_rootTrie = NULL;
	MemoryContext fc_ccxt = CurrentMemoryContext;
	tsearch_readline_state fc_trst;
	volatile bool fc_skip;

	fc_filename = get_tsearch_config_filename(fc_filename, "rules");
	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open unaccent file \"%s\": %m",
						fc_filename)));

	do
	{
		/*
		 * pg_do_encoding_conversion()（由tsearch_readline()调用）将
		 * 在当前区域中发现不可转换的字符时引发异常。我们只跳过这类行，
		 * 继续处理下一行。
		 */
		fc_skip = true;

		PG_TRY();
		{
			char	   *fc_line;

			while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
			{
				/*----------
				 * 每行的格式必须是"src"或"src trg"，其中
				 * src和trg是由空白字符分隔的一个或多个非空白字符序列。
				 * 行首或行尾的空白将被忽略。如果省略trg，则使用空
				 * 字符串作为替换。
				 *
				 * 我们使用一个简单的状态机，状态为
				 *	0	初始状态（在src之前）
				 *	1	在src中
				 *	2	在src后的空白中
				 *	3	在trg中
				 *	4	在trg后的空白中
				 *	-1	检测到语法错误
				 *----------
				 */
				int			fc_state;
				char	   *fc_ptr;
				char	   *fc_src = NULL;
				char	   *fc_trg = NULL;
				int			fc_ptrlen;
				int			fc_srclen = 0;
				int			fc_trglen = 0;

				fc_state = 0;
				for (fc_ptr = fc_line; *fc_ptr; fc_ptr += fc_ptrlen)
				{
					fc_ptrlen = pg_mblen(fc_ptr);
					/* 忽略空白，但结束src或trg */
					if (t_isspace(fc_ptr))
					{
						if (fc_state == 1)
							fc_state = 2;
						else if (fc_state == 3)
							fc_state = 4;
						continue;
					}
					switch (fc_state)
					{
						case 0:
							/* src的开始 */
							fc_src = fc_ptr;
							fc_srclen = fc_ptrlen;
							fc_state = 1;
							break;
						case 1:
							/* 继续src */
							fc_srclen += fc_ptrlen;
							break;
						case 2:
							/* trg的开始 */
							fc_trg = fc_ptr;
							fc_trglen = fc_ptrlen;
							fc_state = 3;
							break;
						case 3:
							/* 继续trg */
							fc_trglen += fc_ptrlen;
							break;
						default:
							/* 无效的行格式 */
							fc_state = -1;
							break;
					}
				}

				if (fc_state == 1 || fc_state == 2)
				{
					/* trg被省略，因此使用"" */
					fc_trg = "";
					fc_trglen = 0;
				}

				if (fc_state > 0)
					fc_rootTrie = fc_placeChar(fc_rootTrie,
										 (unsigned char *) fc_src, fc_srclen,
										 fc_trg, fc_trglen);
				else if (fc_state < 0)
					ereport(WARNING,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid syntax: more than two strings in unaccent rule")));

				pfree(fc_line);
			}
			fc_skip = false;
		}
		PG_CATCH();
		{
			ErrorData  *fc_errdata;
			MemoryContext fc_ecxt;

			fc_ecxt = MemoryContextSwitchTo(fc_ccxt);
			fc_errdata = CopyErrorData();
			if (fc_errdata->sqlerrcode == ERRCODE_UNTRANSLATABLE_CHARACTER)
			{
				FlushErrorState();
			}
			else
			{
				MemoryContextSwitchTo(fc_ecxt);
				PG_RE_THROW();
			}
		}
		PG_END_TRY();
	}
	while (fc_skip);

	tsearch_readline_end(&fc_trst);

	return fc_rootTrie;
}

/*
 * findReplaceTo - 在trie中查找最长可能匹配
 *
 * 成功时，返回指向结束子节点的指针，并在*p_matchlen中返回匹配源字符串的长度。
 * 失败时，返回NULL。
 */
static TrieChar * fc_findReplaceTo(TrieChar *fc_node, const unsigned char *fc_src, int fc_srclen,
			  int *fc_p_matchlen)
{
	TrieChar   *fc_result = NULL;
	int			fc_matchlen = 0;

	*fc_p_matchlen = 0;			/* 防止未初始化变量的警告 */

	while (fc_node && fc_matchlen < fc_srclen)
	{
		fc_node = fc_node + fc_src[fc_matchlen];
		fc_matchlen++;

		if (fc_node->replaceTo)
		{
			fc_result = fc_node;
			*fc_p_matchlen = fc_matchlen;
		}

		fc_node = fc_node->nextChar;
	}

	return fc_result;
}

PG_FUNCTION_INFO_V1(unaccent_init);
Datum unaccent_init(PG_FUNCTION_ARGS)
{
	List	   *fc_dictoptions = (List *) PG_GETARG_POINTER(0);
	TrieChar   *fc_rootTrie = NULL;
	bool		fc_fileloaded = false;
	ListCell   *fc_l;

	foreach(fc_l, fc_dictoptions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);

		if (strcmp(fc_defel->defname, "rules") == 0)
		{
			if (fc_fileloaded)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("multiple Rules parameters")));
			fc_rootTrie = fc_initTrie(defGetString(fc_defel));
			fc_fileloaded = true;
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized Unaccent parameter: \"%s\"",
							fc_defel->defname)));
		}
	}

	if (!fc_fileloaded)
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("missing Rules parameter")));
	}

	PG_RETURN_POINTER(fc_rootTrie);
}

PG_FUNCTION_INFO_V1(unaccent_lexize);
Datum unaccent_lexize(PG_FUNCTION_ARGS)
{
	TrieChar   *fc_rootTrie = (TrieChar *) PG_GETARG_POINTER(0);
	char	   *fc_srcchar = (char *) PG_GETARG_POINTER(1);
	int32		fc_len = PG_GETARG_INT32(2);
	char	   *fc_srcstart = fc_srcchar;
	TSLexeme   *fc_res;
	StringInfoData fc_buf;

	/* 仅在需要时为缓冲区分配存储 */
	fc_buf.data = NULL;

	while (fc_len > 0)
	{
		TrieChar   *fc_node;
		int			fc_matchlen;

		fc_node = fc_findReplaceTo(fc_rootTrie, (unsigned char *) fc_srcchar, fc_len,
							 &fc_matchlen);
		if (fc_node && fc_node->replaceTo)
		{
			if (fc_buf.data == NULL)
			{
				/* 初始化缓冲区 */
				initStringInfo(&fc_buf);
				/* 插入我们已经跳过的任何数据 */
				if (fc_srcchar != fc_srcstart)
					appendBinaryStringInfo(&fc_buf, fc_srcstart, fc_srcchar - fc_srcstart);
			}
			appendBinaryStringInfo(&fc_buf, fc_node->replaceTo, fc_node->replacelen);
		}
		else
		{
			fc_matchlen = pg_mblen(fc_srcchar);
			if (fc_buf.data != NULL)
				appendBinaryStringInfo(&fc_buf, fc_srcchar, fc_matchlen);
		}

		fc_srcchar += fc_matchlen;
		fc_len -= fc_matchlen;
	}

	/* 仅在至少进行了一个替换时返回结果 */
	if (fc_buf.data != NULL)
	{
		fc_res = (TSLexeme *) palloc0(sizeof(TSLexeme) * 2);
		fc_res->lexeme = fc_buf.data;
		fc_res->flags = TSL_FILTER;
	}
	else
		fc_res = NULL;

	PG_RETURN_POINTER(fc_res);
}

/*
 * 字典的函数包装
 */
PG_FUNCTION_INFO_V1(unaccent_dict);
Datum unaccent_dict(PG_FUNCTION_ARGS)
{
	text	   *fc_str;
	int			fc_strArg;
	Oid			fc_dictOid;
	TSDictionaryCacheEntry *fc_dict;
	TSLexeme   *fc_res;

	if (PG_NARGS() == 1)
	{
		/*
		 * 使用与此函数在同一模式中的"unaccent"字典。
		 */
		Oid			fc_procnspid = get_func_namespace(fcinfo->flinfo->fn_oid);
		const char *fc_dictname = "unaccent";

		fc_dictOid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid,
								  PointerGetDatum(fc_dictname),
								  ObjectIdGetDatum(fc_procnspid));
		if (!OidIsValid(fc_dictOid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("text search dictionary \"%s.%s\" does not exist",
							get_namespace_name(fc_procnspid), fc_dictname)));
		fc_strArg = 0;
	}
	else
	{
		fc_dictOid = PG_GETARG_OID(0);
		fc_strArg = 1;
	}
	fc_str = PG_GETARG_TEXT_PP(fc_strArg);

	fc_dict = lookup_ts_dictionary_cache(fc_dictOid);

	fc_res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(fc_dict->lexize),
													 PointerGetDatum(fc_dict->dictData),
													 PointerGetDatum(VARDATA_ANY(fc_str)),
													 Int32GetDatum(VARSIZE_ANY_EXHDR(fc_str)),
													 PointerGetDatum(NULL)));

	PG_FREE_IF_COPY(fc_str, fc_strArg);

	if (fc_res == NULL)
	{
		PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(fc_strArg));
	}
	else if (fc_res->lexeme == NULL)
	{
		pfree(fc_res);
		PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(fc_strArg));
	}
	else
	{
		text	   *fc_txt = cstring_to_text(fc_res->lexeme);

		pfree(fc_res->lexeme);
		pfree(fc_res);

		PG_RETURN_TEXT_P(fc_txt);
	}
}
